package fr.auzaiphe.admin.model;

import java.net.InetAddress;
import java.util.ArrayList;

import javax.swing.JOptionPane;

import fr.auzaiphe.admin.view.AdminView;
import fr.auzaiphe.commun.Model;
import fr.auzaiphe.commun.Outils;
import fr.auzaiphe.commun.reseau.PackageUpdateSousChronoJoueur;
import fr.auzaiphe.commun.reseau.PacketAlerte;
import fr.auzaiphe.commun.reseau.PacketAlerteJoueur;
import fr.auzaiphe.commun.reseau.PacketAnswerConnexion;
import fr.auzaiphe.commun.reseau.PacketError;
import fr.auzaiphe.commun.reseau.PacketInitDataParty;
import fr.auzaiphe.commun.reseau.PacketStartParty;
import fr.auzaiphe.commun.reseau.PacketStopParty;
import fr.auzaiphe.commun.reseau.PacketSynchroIndiceJoueurConnecte;
import fr.auzaiphe.commun.reseau.PacketSynchroJoueur;
import fr.auzaiphe.commun.reseau.PacketUpdateChronoGeneral;
import fr.auzaiphe.commun.reseau.ReseauClient;
import fr.auzaiphe.commun.reseau.ReseauServer;
import fr.auzaiphe.joueur.model.IndiceJoueur;

/**
 * Modele principal de l'application.
 *
 * @author Jojo
 */
public class AdminModel extends Model
{
	private ReseauServer serveur;   // Pour mettre en place le serveur
	private ReseauClient client; 	// Pour que l'admin puisse r?pondre aux joueurs

	private Scenario scenario;      // Scenario de la partie
	private boolean isPartieOn;     // Indique si la partie est lancer ou non

	private int idJoueurAuthentifier;

	/**
	 * Constructeur
	 *
	 */
	public AdminModel( ReseauServer pServer, ReseauClient pClient )
	{
		this.serveur = pServer;
		this.client = pClient;

		this.scenario = new Scenario(this);
		this.isPartieOn = false;

		this.setChanged();
		this.notifyObservers();

		this.setIdJoueurAuthentifier(-1);
	}

	/**
	 * Fermeture de la base de donnée
	 */
	public void closeBDD()
	{
		this.scenario.closeBdd();
	}

	/**
	 * Action faites lors de la fermeture de la selection
	 * du selecteur enregistrer sous
	 * @param pPath
	 */
	public void enregistrerSous(String pPath)
	{
		this.scenario.enregistreSous( pPath );
	}

	/**
	 * Action faites lors de la fermeture de la selection
	 * du selecteur ouvrir un fichier murder
	 * @param pPath
	 */
	public void charger(String pPath)
	{
		this.scenario.charger( pPath );
		this.serveur.broadcast( new PacketInitDataParty( this.isPartieOn, this.getJoueurs(), this.scenario.getChrono().getSecondesRestantes(), this.scenario.getPrixIndice() ) );

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALL );
	}

	/**
	 * Action faites lors de l'enregistrement du scénario
	 */
	public void enregistrer()
	{
		this.scenario.enregistrer();

		this.setChanged();
		this.notifyObservers();
	}

	/**
	 * Ouvre un nouveau fichier .murder
	 */
	public void ouvrirNouveau()
	{
		this.scenario.ouvrirNouveau();

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALL );
	}

	/**
	 * Lance la partie
	 */
	public void lancerPartie()
	{
		this.isPartieOn = true;
		this.scenario.lancerPartie();
		this.serveur.broadcast( new PacketStartParty() );
	}

	/**
	 * Stop la partie
	 */
	public void stopperPartie()
	{
		this.isPartieOn = false;
		this.scenario.stopperPartie();
		this.serveur.broadcast( new PacketStopParty( this.scenario.getChrono().getSecondesRestantes() ) );
	}

	/**
	 * Informe la vue qu'elle doit se mettre à jour
	 * Essentiellement utile pour les Chronos
	 */
	@Override
	public void updateVue()
	{
		this.setChanged();
		this.notifyObservers();
	}

	/**
	 * getJoueurs
	 * @return Sous forme de liste de string, la liste des joueurs a afficher dans le tableau de la view.
	 */
	public ArrayList<String[]> getJoueurs()
	{
		ArrayList<String[]> listeRetour = new ArrayList<String[]>();
		ArrayList<Joueur> listeJoueurs = this.scenario.getListeJoueurs();
		for(Joueur j : listeJoueurs)
		{
			String[] aAjouter = {
					j.getNomPersonnage(),
					j.getPrenomPersonnage(),
					Outils.secondToHMinSec(j.getTempsRestant())+"",
					j.getNbIndices()+""
			};
			listeRetour.add(aAjouter);
		}
		return listeRetour;
	}

	public ArrayList<String[]> getAlertes()
	{
		ArrayList<String[]> listeRetour = new ArrayList<String[]>();
		ArrayList<Alerte> listeAlertes = this.scenario.getListeAlertes();
		for(Alerte a : listeAlertes)
		{
			String lien = a.getLienFichier();
			String[] str2Add = {
					a.getTitre(),
					(lien.equals(""))?"":(lien.substring(lien.lastIndexOf("."))),//Extension du fichier B)
					a.getCibleAlerte(),
					Outils.secondToHMinSec(a.getTempsRestant())+" "
			};
			listeRetour.add(str2Add);
		}
		return listeRetour;
	}

	public ArrayList<String[]> getIndices()
	{
		ArrayList<String[]> listeRetour = new ArrayList<String[]>();
		ArrayList<Indice> listeIndices = this.scenario.getListeIndices();
		for(Indice i : listeIndices)
		{
			String descTmp = i.getContenu().getTexte();
			String[] aAjouter = {
				i.getTitle(),
				(descTmp.length()>15)?descTmp.substring(0, 15):descTmp,
				"" + i.getId()
			};
			listeRetour.add(aAjouter);
		}
		return listeRetour;
	}

	public ArrayList<Evenement> getNotifications()
	{
		return this.scenario.getEvenements();
	}

	/**
	 * Ajout d'un joueur dans le modèle a partir de l'interface. Modification du modele : setChanged + notifyObs.
	 * TODO : Ajouter un joueur soit dans le scenar soit dans la partie en fonction de l'etait du modèle.
	 * @param pNomPersonnage
	 * @param pPrenomPersonnage
	 * @param pBackground
	 * @param pPassword
	 * @param pTemps
	 */
	public void addJoueur(String pNomPersonnage,
						  String pPrenomPersonnage,
						  String pBackground,
						  String pPassword,
						  int pTemps)
	{
		this.scenario.addJoueur(pNomPersonnage, pPrenomPersonnage, pBackground, pPassword, pTemps);

		this.serveur.broadcast( new PacketSynchroJoueur( this.getJoueurs() ) );

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_JOUEUR );
	}

	public void saveScenario(String pContenuScenario, int pChrono)
	{
		this.getScenario().saveScenario( pContenuScenario, pChrono );

		this.serveur.broadcast( new PacketUpdateChronoGeneral( pChrono ) );

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALL );
	}

	/**
	 * Mise à jour d'un joueur dans le modèle. Modification du modele : setChanged + notifyObs.
	 * TODO : Ajouter un joueur soit dans le scenar soit dans la partie en fonction de l'etait du modèle.
	 * @param pNomPersonnage
	 * @param pPrenomPersonnage
	 * @param pBackground
	 * @param pPassword
	 * @param pTemps
	 */
	public void updateJoueur(String pOldNom,
			              String pOldPrenom,
						  String pNomPersonnage,
						  String pPrenomPersonnage,
						  String pBackground,
						  String pPassword,
						  int pTemps)
	{
		this.scenario.updateJoueur(pOldNom, pOldPrenom, pNomPersonnage, pPrenomPersonnage, pBackground, pPassword, pTemps);

		this.client.send(new PackageUpdateSousChronoJoueur(pTemps, pNomPersonnage, pPassword));

		this.serveur.broadcast( new PacketSynchroJoueur( this.getJoueurs() ) );

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_JOUEUR );
	}

	/**
	 *
	 * @param pId
	 * @param pJoueursVisibles
	 * @param pJoueursLies
	 * @param pTitre
	 * @param pContenu
	 * @param pPath
	 * @param pDebut
	 * @param pFin
	 */
	public void updateIndice(int pId, ArrayList<Joueur> pJoueursVisibles,
			ArrayList<Joueur> pJoueursLies, String pTitre, Contenu pContenu, int pDebut, int pFin)
	{
		this.scenario.updateIndice( pId, pJoueursVisibles, pJoueursLies, pTitre, pContenu, pDebut, pFin);

		Joueur j = this.getJoueurConnecte();
		if ( j != null )
		{
			this.serveur.broadcast( new PacketSynchroIndiceJoueurConnecte( this.toListeIndiceJoueur( j.getIndicesDispo() ) ) );
		}

		this.setChanged();
		/* On refresh joueur pour remettre à jours les nombres d'indices de chacun.
		 * Le tableau d'indice se remettra automatique à jour car l'orga aura le focus dessus
		 */
		this.notifyObservers( AdminView.REFRESH_JOUEUR );
	}

	public void updateAlerte(int pId,
						  boolean pCibleAll,
						  boolean pCibleAdmin,
						  String pNomJoueur,
						  String pPrenomJoueur,
						  String pNomChrono,
						  String pPrenomChrono,
						  int pActivation,
						  String pTitre,
						  String pTexte,
						  String pLienFichier)
	{
		this.scenario.updateAlerte(pId,pCibleAll, pCibleAdmin, pNomJoueur, pPrenomJoueur, pNomChrono, pPrenomChrono, pActivation,pTitre, pTexte, pLienFichier);

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALERTE );
	}

	/**
	 * Mise a jour du prix des indices
	 * @param pPrix
	 */
	public void updatePrice(int pPrix)
	{
		this.scenario.setPrixIndice(pPrix);

	}

	/**
	 * Ajout d'une alerte à partir de l'interface.
	 * @param pCibleAll
	 * @param pCibleAdmin
	 * @param pCible
	 * @param pChronoCible
	 * @param pActivation
	 * @param pTexte
	 * @param pLienFichier
	 */
	public void addAlerte(boolean pCibleAll,
						  boolean pCibleAdmin,
						  String pNomJoueur,
						  String pPrenomJoueur,
						  String pNomChrono,
						  String pPrenomChrono,
						  int pActivation,
						  String pTitre,
						  String pTexte,
						  String pLienFichier)
	{
		this.scenario.addAlerte(pCibleAll, pCibleAdmin, pNomJoueur, pPrenomJoueur, pNomChrono, pPrenomChrono, pActivation,pTitre, pTexte, pLienFichier);
		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALERTE );
	}

	/**
	 * Ajoute un indice à la liste des indices du scénario et à la base de donnée.
	 * Met à jour en même temps les listes d'indices des joueurs.
	 * @param pVisibles - Joueurs qui peuvent voir l'indice
	 * @param pLies - Joueurs liés à l'indice
	 * @param pContenu - Contenu de l'indice
	 * @param pLienFichier - Lien vers le fichier lié à l'indice
	 * @param pTitre - Titre de l'indice
	 * @param pDebutVie - Debut de vie de l'indice
	 * @param pFinVie - Fin de vie de l'indice
	 * @param pOn
	 * @param pOff
	 */
	public void addIndice(ArrayList<Joueur> pVisibles,
						  ArrayList<Joueur> pLies,
						  String pContenu,
						  String pLienFichier,
						  String pTitre,
						  int pDebutVie,
						  int pFinVie,
						  int pOn,
						  int pOff)
	{
		this.scenario.addIndice(pVisibles, pLies, pContenu, pLienFichier, pTitre, pDebutVie, pFinVie, pOn, pOff);

		Joueur j = this.getJoueurConnecte();
		if ( pVisibles.contains( j ) )
		{
			// Envoie paquet synchro indice!
			this.serveur.broadcast( new PacketSynchroIndiceJoueurConnecte( this.toListeIndiceJoueur( j.getIndicesDispo() ) ) );
		}

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_JOUEUR );
	}

	/**
	 * Recupère le joueur connecté à l'application
	 * Si il existe
	 * @return
	 */
	private Joueur getJoueurConnecte()
	{
		if ( this.idJoueurAuthentifier != -1 )
		{
			for ( int i = 0; i < this.scenario.getListeJoueurs().size(); i++ )
			{
				if ( this.scenario.getListeJoueurs().get(i).getId() == this.idJoueurAuthentifier )
				{
					return this.scenario.getListeJoueurs().get(i);
				}
			}
		}
		return null;
	}

	/**
	 * Supprime le joueur du model et informe la vue
	 * @param joueurSupprime
	 */
	public void supprimerJoueur(Joueur joueurSupprime)
	{
		this.scenario.supprimerJoueur(joueurSupprime);

		this.serveur.broadcast( new PacketSynchroJoueur( this.getJoueurs() ) );

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_JOUEUR );
	}

	/**
	 * Supprime l'alerte du model et informe la vue
	 * @param alerteSupprime
	 */
	public void supprimerAlerte(Alerte alerteSupprime)
	{
		this.scenario.supprimerAlerte(alerteSupprime);

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_ALERTE );
	}

	/**
	 * Supprime le joueur du model et informe la vue
	 * @param pIndiceSupprime
	 */
	public void supprimerIndice(Indice pIndiceSupprime)
	{
		Joueur j = this.getJoueurConnecte();
		if ( pIndiceSupprime.getJoueursVisibles().contains( j ) )
		{
			this.scenario.supprimerIndice( pIndiceSupprime );
			this.serveur.broadcast( new PacketSynchroIndiceJoueurConnecte( this.toListeIndiceJoueur( j.getIndicesDispo() ) ) );
		}
		else
		{
			this.scenario.supprimerIndice( pIndiceSupprime );
		}

		this.setChanged();
		this.notifyObservers( AdminView.REFRESH_INDICE );
	}

	/**
	 * Authentification client
	 */
	public void authentifierClient(InetAddress pAdr, String pPseudo, String pPassword)
	{
		System.out.println("Liste joueur taille : " +  this.scenario.listeJoueurs.size() );

		// Parcours de la liste des joueurs
		for( Joueur j : this.scenario.getListeJoueurs() )
		{
			if( j.getPassword().equals(pPassword) && j.getNomPersonnage().equals(pPseudo) )
			{ // Si joueur matcher envoie du message
				j.getChrono().start();
				this.serveur.sendTo( pAdr, new PacketAnswerConnexion( true,
																	  j.getTempsRestant(),
																	  j.getNomPersonnage(),
																	  j.getPrenomPersonnage(),
																	  j.getPassword(),
																	  this.toListeIndiceJoueur( j.getIndicesDispo() ),
																	  pAdr) );

				System.out.println("Joueur "+j.getPrenomPersonnage()+" "+j.getNomPersonnage()+" connecté.");

				this.scenario.getChrono().addEvtConnexion(j, true);
				this.setChanged();
				this.notifyObservers();

				//envoyer tout les alerte non vue
				this.setIdJoueurAuthentifier( j.getId() );
				for (Alerte al : j.getListAlerteNonVisualiser())
				{
					this.sendAlerteJoueur(al);
				}
				j.getListAlerteNonVisualiser().clear();

				return;
			}
		}
		System.out.println("Joueur non trouvé");
		this.serveur.sendTo( pAdr, new PacketAnswerConnexion( false, 0, pAdr) );
	}


	/**
	 * Deconnexion d'un joueur
	 */
	public void deconnecterClient(String pNom, String pPassword, int pTempsRestant)
	{
		this.setIdJoueurAuthentifier(-1);
		for (Joueur j : this.scenario.getListeJoueurs())
			if(j.getPassword().equals(pPassword) && j.getNomPersonnage().equals(pNom))
			{
				j.setTempsRestant(pTempsRestant);
				this.scenario.getChrono().addEvtConnexion(j, false);
				j.getChrono().interrupt();
				this.setChanged();
				this.notifyObservers();
			}
	}

	/**
	 * Rend disponible au joueur indiqué un indice auquel il n'avait pas accès.
	 * @param pNom Nom du joueur
	 * @param pPassword Password
	 */
	public void buyIndice (String pNom, String pPassword)
	{
		//Recuperation des indices non visibles pour le personnage qui sontentre debut et fin de vie.
		for(Joueur j : this.scenario.getListeJoueurs())
		{
			if(j.getNomPersonnage().equals(pNom) && j.getPassword().equals(pPassword))
			{
				ArrayList<Indice> indicesNonDispos = new ArrayList<Indice>();
				for(Indice i : this.scenario.getListeIndices())
				{
					if(
							!j.getIndicesDispo().contains(i) &&
							i.getDebutVie() > this.scenario.chrono.getSecondesRestantes() &&
							i.getFinVie() < this.scenario.chrono.getSecondesRestantes()
					)
					{
						indicesNonDispos.add(i);
					}
				}
				if(indicesNonDispos.size()>0)
				{
					Indice randomIndice = indicesNonDispos.get((int)(indicesNonDispos.size()*Math.random()));
					randomIndice.addJoueurVisible(j);
					j.giveIndice(randomIndice);
					j.setTempsRestant(j.getTempsRestant()-this.scenario.getPrixIndice());
					this.client.send(new PackageUpdateSousChronoJoueur(j.getTempsRestant(), j.getNomPersonnage(), j.getPassword()));
					this.scenario.getChrono().addEvtDecouverteIndice(j, randomIndice);
				}
				else
					this.client.send(new PacketError("Aucun indice n'est disponible.", false));

			}
			this.serveur.broadcast( new PacketSynchroIndiceJoueurConnecte( this.toListeIndiceJoueur( j.getIndicesDispo() ) ) );
			this.setChanged();
			this.notifyObservers();
			break;
		}
	}

	public void putError(String text)
	{
		JOptionPane.showMessageDialog(null, text, "Erreur admin", JOptionPane.ERROR_MESSAGE);
	}

	/*
	 * Fonctions réseau
	 */

	private ArrayList<IndiceJoueur> toListeIndiceJoueur(ArrayList<Indice> indicesDispo)
	{
		ArrayList<IndiceJoueur> listeReturn = new ArrayList<IndiceJoueur>();

		for( int i = 0; i < indicesDispo.size(); i++ )
		{
			listeReturn.add( new IndiceJoueur( indicesDispo.get(i).getTitle(), indicesDispo.get(i).getContenu() ) );
		}

		return listeReturn;
	}

	public void sendAlerte(Alerte pAlerte)
	{
		this.client.send( new PacketAlerte( pAlerte.getTitre(),
											pAlerte.getTexte(),
											pAlerte.getLienFichier(),
											this.client.getSocket().getInetAddress() ) );
	}

	public void sendAlerteJoueur(Alerte pAlerte)
	{
		this.client.send( new PacketAlerteJoueur(	pAlerte.getTitre(),
													pAlerte.getTexte(),
													pAlerte.getLienFichier(),
													this.client.getSocket().getInetAddress() ) );
	}
	/*
	 * Getters et Setters
	 */

	public ReseauClient getClient()
	{
		return this.client;
	}

	public Scenario getScenario()
	{
		return this.scenario;
	}

	/**
	 * @return the idJoueurAuthentifier
	 */
	public int getIdJoueurAuthentifier()
	{
		return this.idJoueurAuthentifier;
	}

	/**
	 * @param idJoueurAuthentifier the idJoueurAuthentifier to set
	 */
	public void setIdJoueurAuthentifier(int idJoueurAuthentifier)
	{
		this.idJoueurAuthentifier = idJoueurAuthentifier;
	}

	/**
	 *
	 * @return Si la partie est en cours.
	 */
	public boolean getIsPartieOn()
	{
		return this.isPartieOn;
	}
}
